﻿using System;
using System.Text;
using Adaptive.Agrona;
using Adaptive.Agrona.Concurrent;

#pragma warning disable CS8618
namespace Unity.Type.BinaryType
{
    public sealed class Metadata
    {
        public const int BlockLength = 68;
        public const int TemplateId = 1;
        public const int SchemaId = 1;
        public const int SchemaVersion = 0;
        public const string SemanticType = "";

        private const ByteOrder DefaultByteOrder = ByteOrder.LittleEndian;

        private readonly Metadata _parentMessage;
        private IMutableDirectBuffer _buffer;
        private int _offset;
        private int _limit;
        private int _actingBlockLength;
        private int _actingVersion;

        public int Offset => _offset;

        public Metadata()
        {
            _parentMessage = this;
            _buffer = new UnsafeBuffer(new byte[BlockLength]);
        }

        public void WrapForEncode(IMutableDirectBuffer buffer, int offset)
        {
            _buffer = buffer;
            _offset = offset;
            _actingBlockLength = BlockLength;
            _actingVersion = SchemaVersion;
            Limit = offset + _actingBlockLength;
        }

        public void WrapForEncodeAndApplyHeader(IMutableDirectBuffer buffer, int offset, MessageHeader headerEncoder)
        {
            headerEncoder.Wrap(buffer, offset, SchemaVersion);
            headerEncoder.BlockLength = BlockLength;
            headerEncoder.TemplateId = TemplateId;
            headerEncoder.SchemaId = SchemaId;
            headerEncoder.Version = SchemaVersion;

            WrapForEncode(buffer, offset + MessageHeader.Size);
        }

        public void WrapForDecode(IMutableDirectBuffer buffer, int offset, int actingBlockLength, int actingVersion)
        {
            _buffer = buffer;
            _offset = offset;
            _actingBlockLength = actingBlockLength;
            _actingVersion = actingVersion;
            Limit = offset + _actingBlockLength;
        }

        public int Size => _limit - _offset;

        public int Limit
        {
            get => _limit;
            set
            {
                _buffer.CheckLimit(value);
                _limit = value;
            }
        }


        public const int UnityIdId = 1;
        public const int UnityIdSinceVersion = 0;
        public const int UnityIdDeprecated = 0;
        public bool UnityIdInActingVersion()
        {
            return _actingVersion >= UnityIdSinceVersion;
        }

        public const int UnityIdOffset = 0;

        public static string UnityIdMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public const byte UnityIdNullValue = 0;
        public const byte UnityIdMinValue = 32;
        public const byte UnityIdMaxValue = 126;

        public const int UnityIdLength = 34;

        public byte GetUnityId(int index)
        {
            if ((uint)index >= 34)
            {
                MetadataExceptionHelper.ThrowIndexOutOfRangeException(index);
            }

            return _buffer.GetByte(_offset + 0 + (index * 1));
        }

        public void SetUnityId(int index, byte value)
        {
            if ((uint)index >= 34)
            {
                MetadataExceptionHelper.ThrowIndexOutOfRangeException(index);
            }

            _buffer.PutByte(_offset + 0 + (index * 1), value);
        }

        public ReadOnlySpan<byte> UnityId
        {
            get => UnityIdAsSpan();

            set => value.CopyTo(_buffer.ByteArray.AsSpan(_offset + 0, UnityIdLength));
        }

        public Span<byte> UnityIdAsSpan()
        {
            byte[] unityIdData = new byte[UnityIdLength];
            _buffer.GetBytes(_offset + 0, unityIdData, 0, UnityIdLength);
            return new Span<byte>(unityIdData);
        }

        public const string UnityIdCharacterEncoding = "ASCII";
        public static Encoding UnityIdResolvedCharacterEncoding = Encoding.GetEncoding(UnityIdCharacterEncoding);


        public int GetUnityId(byte[] dst, int dstOffset)
        {
            const int length = 34;
            return GetUnityId(new Span<byte>(dst, dstOffset, length));
        }

        public int GetUnityId(Span<byte> unityIdData)
        {
            const int length = 34;
            if (unityIdData.Length < length)
            {
                MetadataExceptionHelper.ThrowWhenSpanLengthTooSmall(unityIdData.Length);
            }

            _buffer.GetBytes(_offset + 0, unityIdData.ToArray());
            return length;
        }

        public void SetUnityId(byte[] binaryData, int offset)
        {
            SetUnityId(new ReadOnlySpan<byte>(binaryData, offset, binaryData.Length - offset));
        }

        public void SetUnityId(ReadOnlySpan<byte> unityIdData)
        {
            const int length = 34;
            if (unityIdData.Length > length)
            {
                MetadataExceptionHelper.ThrowWhenSpanLengthTooLarge(unityIdData.Length);
            }

            _buffer.PutBytes(_offset + 0, unityIdData.ToArray());
        }

        public void SetUnityId(string value)
        {
            MetadataHelper.SetNullTerminatedBytesFromString(_buffer, value, _offset + 0, UnityIdLength, UnityIdNullValue);
        }
        public string GetUnityId()
        {
            return MetadataHelper.GetStringFromNullTerminatedBytes(_buffer, _offset + 0, UnityIdLength, UnityIdNullValue);
        }

        public const int TimestampId = 2;
        public const int TimestampSinceVersion = 0;
        public const int TimestampDeprecated = 0;
        public bool TimestampInActingVersion()
        {
            return _actingVersion >= TimestampSinceVersion;
        }

        public const int TimestampOffset = 34;

        public static string TimestampMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public const long TimestampNullValue = -9223372036854775808L;
        public const long TimestampMinValue = -9223372036854775807L;
        public const long TimestampMaxValue = 9223372036854775807L;

        public long Timestamp
        {
            get => _buffer.GetLong(_offset + 34, DefaultByteOrder);
            set => _buffer.PutLong(_offset + 34, value, DefaultByteOrder);
        }


        public const int VersionId = 3;
        public const int VersionSinceVersion = 0;
        public const int VersionDeprecated = 0;
        public bool VersionInActingVersion()
        {
            return _actingVersion >= VersionSinceVersion;
        }

        public const int VersionOffset = 42;

        public static string VersionMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public const long VersionNullValue = -9223372036854775808L;
        public const long VersionMinValue = -9223372036854775807L;
        public const long VersionMaxValue = 9223372036854775807L;

        public long Version
        {
            get => _buffer.GetLong(_offset + 42, DefaultByteOrder);
            set => _buffer.PutLong(_offset + 42, value, DefaultByteOrder);
        }


        public const int ChangerCsiIdId = 4;
        public const int ChangerCsiIdSinceVersion = 0;
        public const int ChangerCsiIdDeprecated = 0;
        public bool ChangerCsiIdInActingVersion()
        {
            return _actingVersion >= ChangerCsiIdSinceVersion;
        }

        public const int ChangerCsiIdOffset = 50;

        public static string ChangerCsiIdMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public const int ChangerCsiIdNullValue = -2147483648;
        public const int ChangerCsiIdMinValue = -2147483647;
        public const int ChangerCsiIdMaxValue = 2147483647;

        public int ChangerCsiId
        {
            get => _buffer.GetInt(_offset + 50, DefaultByteOrder);
            set => _buffer.PutInt(_offset + 50, value, DefaultByteOrder);
        }


        public const int EntityTypeId = 5;
        public const int EntityTypeSinceVersion = 0;
        public const int EntityTypeDeprecated = 0;
        public bool EntityTypeInActingVersion()
        {
            return _actingVersion >= EntityTypeSinceVersion;
        }

        public const int EntityTypeOffset = 54;

        public static string EntityTypeMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public EntityType EntityType
        {
            get => (EntityType)_buffer.GetByte(_offset + 54);
            set => _buffer.PutByte(_offset + 54, (byte)value);
        }


        public const int EventTypeId = 6;
        public const int EventTypeSinceVersion = 0;
        public const int EventTypeDeprecated = 0;
        public bool EventTypeInActingVersion()
        {
            return _actingVersion >= EventTypeSinceVersion;
        }

        public const int EventTypeOffset = 55;

        public static string EventTypeMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public EventType EventType
        {
            get => (EventType)_buffer.GetByte(_offset + 55);
            set => _buffer.PutByte(_offset + 55, (byte)value);
        }


        public const int FormatVerId = 7;
        public const int FormatVerSinceVersion = 0;
        public const int FormatVerDeprecated = 0;
        public bool FormatVerInActingVersion()
        {
            return _actingVersion >= FormatVerSinceVersion;
        }

        public const int FormatVerOffset = 56;

        public static string FormatVerMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public const int FormatVerNullValue = -2147483648;
        public const int FormatVerMinValue = -2147483647;
        public const int FormatVerMaxValue = 2147483647;

        public int FormatVer
        {
            get => _buffer.GetInt(_offset + 56, DefaultByteOrder);
            set => _buffer.PutInt(_offset + 56, value, DefaultByteOrder);
        }


        public const int VersionDeleteId = 8;
        public const int VersionDeleteSinceVersion = 0;
        public const int VersionDeleteDeprecated = 0;
        public bool VersionDeleteInActingVersion()
        {
            return _actingVersion >= VersionDeleteSinceVersion;
        }

        public const int VersionDeleteOffset = 60;

        public static string VersionDeleteMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public const long VersionDeleteNullValue = -9223372036854775808L;
        public const long VersionDeleteMinValue = -9223372036854775807L;
        public const long VersionDeleteMaxValue = 9223372036854775807L;

        public long VersionDelete
        {
            get => _buffer.GetLong(_offset + 60, DefaultByteOrder);
            set => _buffer.PutLong(_offset + 60, value, DefaultByteOrder);
        }

        public override int GetHashCode()
        {
            int hash = 42;
            hash = (hash * 1) + UnityId.ToString().GetHashCode();
            hash = (hash * 2) + Version.GetHashCode();
            hash = (hash * 3) + ChangerCsiId.GetHashCode();
            hash = (hash * 4) + Timestamp.GetHashCode();
            hash = (hash * 5) + VersionDelete.GetHashCode();
            hash = (hash * 6) + EntityType.GetHashCode();
            hash = (hash * 7) + EventType.GetHashCode();
            hash = (hash * 8) + FormatVer.GetHashCode();
            return hash;
        }

        public override bool Equals(object obj)
        {
            if (this == obj) return true;
            if (obj == null || GetType() != obj.GetType()) return false;
            Metadata metadata = (Metadata)obj;
            if (Timestamp != metadata.Timestamp) return false;
            if (Version != metadata.Version) return false;
            if (ChangerCsiId != metadata.ChangerCsiId) return false;
            if (VersionDelete != metadata.VersionDelete) return false;
            if (!UnityId.SequenceEqual(metadata.UnityId)) return false;
            if (EntityType != metadata.EntityType) return false;
            if (EventType != metadata.EventType) return false;
            return true;
        }

        public override string ToString()
        {
            var sb = new StringBuilder(100);
            BuildString(sb);
            return sb.ToString();
        }

        internal void BuildString(StringBuilder builder)
        {
            if (_buffer == null)
            {
                throw new InvalidOperationException("Can't provide string value of null buffer");
            }

            int originalLimit = Limit;
            Limit = _offset + _actingBlockLength;
            builder.Append("[Metadata](sbeTemplateId=");
            builder.Append(TemplateId);
            builder.Append("|sbeSchemaId=");
            builder.Append(SchemaId);
            builder.Append("|sbeSchemaVersion=");
            if (_parentMessage._actingVersion != SchemaVersion)
            {
                builder.Append(_parentMessage._actingVersion);
                builder.Append('/');
            }
            builder.Append(SchemaVersion);
            builder.Append("|sbeBlockLength=");
            if (_actingBlockLength != BlockLength)
            {
                builder.Append(_actingBlockLength);
                builder.Append('/');
            }
            builder.Append(BlockLength);
            builder.Append("):");

            builder.Append("UnityId=");
            for (int i = 0; i < UnityIdLength && GetUnityId(i) > 0; ++i)
            {
                builder.Append((char)GetUnityId(i));
            }
            builder.Append('|');
            builder.Append("Timestamp=");
            builder.Append(Timestamp);
            builder.Append('|');
            builder.Append("Version=");
            builder.Append(Version);
            builder.Append('|');
            builder.Append("ChangerCsiId=");
            builder.Append(ChangerCsiId);
            builder.Append('|');
            builder.Append("EntityType=");
            builder.Append(EntityType);
            builder.Append('|');
            builder.Append("EventType=");
            builder.Append(EventType);
            builder.Append('|');
            builder.Append("FormatVer=");
            builder.Append(FormatVer);
            builder.Append('|');
            builder.Append("VersionDelete=");
            builder.Append(VersionDelete);

            Limit = originalLimit;

        }
    }
}
